Crate axum_sqlx_tx

source ·
Expand description

Request-bound SQLx transactions for axum.

Tx is an axum extractor for obtaining a transaction that’s bound to the HTTP request. A transaction begins the first time the extractor is used for a request, and is then stored in request extensions for use by other middleware/handlers. The transaction is resolved depending on the status code of the eventual response – successful (HTTP 2XX or 3XX) responses will cause the transaction to be committed, otherwise it will be rolled back.

This behaviour is often a sensible default, and using the extractor (e.g. rather than directly using [sqlx::Transaction]s) means you can’t forget to commit the transactions!

Usage

To use the Tx extractor, you must first add Layer to your app:

let pool = /* any sqlx::Pool */
let app = axum::Router::new()
    // .route(...)s
    .layer(axum_sqlx_tx::Layer::new(pool));

You can then simply add Tx as an argument to your handlers:

use axum_sqlx_tx::Tx;
use sqlx::Sqlite;

async fn create_user(mut tx: Tx<Sqlite>, /* ... */) {
    // `&mut Tx` implements `sqlx::Executor`
    let user = sqlx::query("INSERT INTO users (...) VALUES (...)")
        .fetch_one(&mut tx)
        .await
        .unwrap();

    // `Tx` also implements `Deref<Target = sqlx::Transaction>` and `DerefMut`
    use sqlx::Acquire;
    let inner = tx.begin().await.unwrap();
    /* ... */
}

If you forget to add the middleware you’ll get Error::MissingExtension (internal server error) when using the extractor. You’ll also get an error (Error::OverlappingExtractors) if you have multiple Tx arguments in a single handler, or call Tx::from_request multiple times in a single middleware.

Error handling

axum requires that middleware do not return errors, and that the errors returned by extractors implement IntoResponse. By default, Error is used by Layer and Tx to convert errors into HTTP 500 responses, with the error’s Display value as the response body, however it’s generally not a good practice to return internal error details to clients!

To make it easier to customise error handling, both Layer and Tx have a second generic type parameter, E, that can be used to override the error type that will be used to convert the response.

use axum::response::IntoResponse;
use axum_sqlx_tx::Tx;
use sqlx::Sqlite;

struct MyError(axum_sqlx_tx::Error);

// Errors must implement From<axum_sqlx_tx::Error>
impl From<axum_sqlx_tx::Error> for MyError {
    fn from(error: axum_sqlx_tx::Error) -> Self {
        Self(error)
    }
}

// Errors must implement IntoResponse
impl IntoResponse for MyError {
    fn into_response(self) -> axum::response::Response {
        // note that you would probably want to log the error or something
        (http::StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response()
    }
}

// Change the layer error type
let app = axum::Router::new()
    // .route(...)s
    .layer(axum_sqlx_tx::Layer::new_with_error::<MyError>(pool));

// Change the extractor error type
async fn create_user(mut tx: Tx<Sqlite, MyError>, /* ... */) {
    /* ... */
}

Examples

See examples/ in the repo for more examples.

Structs

A tower_layer::Layer that enables the Tx extractor.
A tower_service::Service that enables the Tx extractor.
An axum extractor for a database transaction.

Enums

Possible errors when extracting Tx from a request.